/**
 * The MIT License (MIT)
 *
 * Author: Hongtai Liu (lht856@foxmail.com)
 *
 * Copyright (C) 2019  Seeed Technology Co.,Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef _SPI_H_INCLUDED
#define _SPI_H_INCLUDED

#include "fsl_lpspi.h"
#include <Arduino.h>

// SPI_HAS_TRANSACTION means SPI has
//   - beginTransaction()
//   - endTransaction()
//   - usingInterrupt()
//   - SPISetting(clock, bitOrder, dataMode)
#define SPI_HAS_TRANSACTION 1

// SPI_HAS_NOTUSINGINTERRUPT means that SPI has notUsingInterrupt() method
#define SPI_HAS_NOTUSINGINTERRUPT 1

#define SPI_MODE0 0x00
#define SPI_MODE1 0x01
#define SPI_MODE2 0x02
#define SPI_MODE3 0x03

typedef struct _spi_mode
{
    lpspi_clock_polarity_t _CPOL;
    lpspi_clock_phase_t _CPHA;
} spi_mode;

const spi_mode spiMode[] = {{kLPSPI_ClockPolarityActiveHigh, kLPSPI_ClockPhaseFirstEdge},
                            {kLPSPI_ClockPolarityActiveHigh, kLPSPI_ClockPhaseSecondEdge},
                            {kLPSPI_ClockPolarityActiveLow, kLPSPI_ClockPhaseFirstEdge},
                            {kLPSPI_ClockPolarityActiveLow, kLPSPI_ClockPhaseSecondEdge}};

class SPISettings
{
  public:
    SPISettings(uint32_t clock, BitOrder bitOrder, uint8_t dataMode)
    {
        if (__builtin_constant_p(clock))
        {
            init_AlwaysInline(clock, bitOrder, dataMode);
        }
        else
        {
            init_MightInline(clock, bitOrder, dataMode);
        }
    }

    // Default speed set to 4MHz, SPI mode set to MODE 0 and Bit order set to MSB first.
    SPISettings()
    {
        init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0);
    }

    bool operator==(const SPISettings &rhs) const
    {
        if ((this->clockFreq == rhs.clockFreq) && (this->bitOrder == rhs.bitOrder) && (this->dataMode == rhs.dataMode))
        {
            return true;
        }
        return false;
    }

    bool operator!=(const SPISettings &rhs) const
    {
        return !(*this == rhs);
    }

    uint32_t getClockFreq() const
    {
        return clockFreq;
    }
    uint8_t getDataMode() const
    {
        return (uint8_t)dataMode;
    }
    BitOrder getBitOrder() const
    {
        return (bitOrder == kLPSPI_MsbFirst ? MSBFIRST : LSBFIRST);
    }

  private:
    void init_MightInline(uint32_t clock, BitOrder bitOrder, uint8_t dataMode)
    {
        init_AlwaysInline(clock, bitOrder, dataMode);
    }

    void init_AlwaysInline(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) __attribute__((__always_inline__))
    {
        this->clockFreq = clock;

        this->bitOrder = (bitOrder == MSBFIRST ? kLPSPI_MsbFirst : kLPSPI_LsbFirst);

        this->dataMode = dataMode;
    }

    uint32_t clockFreq;
    uint8_t dataMode;
    lpspi_shift_direction_t bitOrder;
    friend class SPIClass;
};

const SPISettings DEFAULT_SPI_SETTINGS = SPISettings();

class SPIClass
{
  public:
    SPIClass(LPSPI_Type *lpspi, uint8_t uc_pinMISO, uint8_t uc_pinSCK, uint8_t uc_pinMOSI);

    byte transfer(uint8_t data);
    uint16_t transfer16(uint16_t data);
    void transfer(void *buf, size_t count);

    // Transaction Functions
    void usingInterrupt(int interruptNumber);
    void notUsingInterrupt(int interruptNumber);
    void beginTransaction(SPISettings settings);
    void endTransaction(void);

    // SPI Configuration methods
    void attachInterrupt();
    void detachInterrupt();

    void begin();
    void end();

    void setBitOrder(BitOrder order);
    void setDataMode(uint8_t uc_mode);
    void setClock(uint32_t uc_div);

  private:
    void init();
    void config(SPISettings settings);

    LPSPI_Type *_lpspi;
    uint8_t _uc_pinMiso;
    uint8_t _uc_pinMosi;
    uint8_t _uc_pinSCK;

    SPISettings _settings;

    bool initialized;
    uint8_t interruptMode;
    char interruptSave;
    uint32_t interruptMask;
};

extern SPIClass SPI3;

#define SPI SPI3

#endif
